#include "dispenseworkholder.h"

DispenseWorkHolder::DispenseWorkHolder(TrayConfig *sensorTrayConfig,
                                       TrayMap *sensorTrayMap,
                                       WorkHolderState *whState,
                                       DispenseHead *leftDH,
                                       DispenseHead *rightDH,
                                       SensorTrayInputElevator *stie,
                                       QObject *parent)
    : Worker(WorkerName::DispenseWorkHolder, parent),
      sensorTrayConfig(sensorTrayConfig),
      sensorTrayMap(sensorTrayMap),
      whState(whState),
      leftDH(leftDH),
      rightDH(rightDH),
      stie(stie)
{
    dispWHConfig = new DispWHConfig(this);
    configFile = new ConfigFile("DispenseWorkHolderConfig", dispWHConfig,
                                QString("%1/dispenseWorkHolderConfig.json").arg(getWorkerConfigDir()));
    configFile->populate();
}

void DispenseWorkHolder::initEventHandle()
{
    dispWHBelt = MotionElementContainer::getIns()->getItem<SCAxis>(AxisName::DispWHBelt);
    dispWHBeltZModule = MotionElementContainer::getIns()->getItem<SingleAxis>(SAxisModuleName::DispWHBeltZ);
    dispWHVacuum = MotionElementContainer::getIns()->getItem<SCVacuum>(VacuumName::DispWHVacuum);
    dispWHStopperCyl = MotionElementContainer::getIns()->getItem<SCCylinder>(CylName::DispWHStopper);
    dispWHLeftTraySensor = MotionElementContainer::getIns()->getItem<SCDI>(DiName::DispWHLeftTraySNR);
    dispWHStageTraySensor = MotionElementContainer::getIns()->getItem<SCDI>(DiName::DispWHStageTraySNR);

    beltRunDir = SCAxis::DirEnumInfo().nameToEnum(dispWHConfig->beltRunDirection());
}

void DispenseWorkHolder::startEventHandle(const WorkerEvent &event)
{
    Q_UNUSED(event);

    hasSynchronizeHasTray = false;
    isStopping = false;
    pushEvent(WorkerEvent("getNewTray"));
}

void DispenseWorkHolder::stopEventHandle()
{
    isStopping = true;
}

void DispenseWorkHolder::allWorkerStopped()
{
    isStopping = false;
}

void DispenseWorkHolder::purgeEventHandle()
{
    purgeEventHandled();
}

void DispenseWorkHolder::resetEventHandle()
{
    isStopping = false;
}

void DispenseWorkHolder::homeWorker(bool moveToReadyPosAfterHome)
{
    leftDH->dispZ->axis()->home(false);
    rightDH->dispZ->axis()->home(false);
    leftDH->dispZ->axis()->waitHomeDone();
    rightDH->dispZ->axis()->waitHomeDone();

    SCCylinder *bwhStopperLeft = MotionElementContainer::getIns()->getItem<SCCylinder>(CylName::BWHStopperLeft);
    dispWHStopperCyl->set(true, false);
    bwhStopperLeft->set(true, false);
    dispWHStopperCyl->wait(true);
    bwhStopperLeft->wait(true);

    leftDH->dispZLockCyl->set(true, false);
    rightDH->dispZLockCyl->set(true, false);

    leftDH->dispXYZ->xAxis()->home(false);
    leftDH->dispXYZ->yAxis()->home(false);
    rightDH->dispXYZ->xAxis()->home(false);
    rightDH->dispXYZ->yAxis()->home(false);
    dispWHBeltZModule->axis()->home(false);
    leftDH->dispXYZ->xAxis()->waitHomeDone();
    leftDH->dispXYZ->yAxis()->waitHomeDone();
    rightDH->dispXYZ->xAxis()->waitHomeDone();
    rightDH->dispXYZ->yAxis()->waitHomeDone();
    dispWHBeltZModule->axis()->waitHomeDone();

    if (moveToReadyPosAfterHome)
    {
        dispWHBeltZModule->moveTo(SAxisPosName::UpPos, false);
        rightDH->moveToReadyPos();
        dispWHBeltZModule->waitArrivedPos(SAxisPosName::UpPos);
    }
}

void DispenseWorkHolder::getNewTray()
{
    checkIsInit();

    if (!hasSynchronizeHasTray)
    {
        synchronizeHasTray();
        if (whState->dispWHHasTray())
        {
            whState->setDispWHTrayHasDoneWork(false);
        }
        hasSynchronizeHasTray = true;
    }
    if (isStopping)
    {
        handleStop();
        return;
    }
    if (!whState->dispWHHasTray())
    {
        getNewTrayImpl();
    }
    if (isStopping)
    {
        handleStop();
        return;
    }
    if (!whState->dispWHTrayHasDoneWork())
    {
        downBeltZ();

        WorkerEvent gotNewTrayEvent("gotNewTray");
        leftDH->processEvent(gotNewTrayEvent);
        rightDH->processEvent(gotNewTrayEvent);

        rightDH->pushEvent(WorkerEvent("performAllSensorPr"));
        rightDH->pushEvent(WorkerEvent("distributeRemainedShotGlueTask"));
    }
}

void DispenseWorkHolder::pushOutTray()
{
    checkIsInit();

    pushOutTrayImpl();

    pushEvent(WorkerEvent("getNewTray"));
}

void DispenseWorkHolder::hasDoneWork()
{
    upBeltZ();
    whState->setDispWHTrayHasDoneWork(true);
}

void DispenseWorkHolder::manuallyGetNewTray(bool showTip)
{
    checkIsInit();

    if (detectReallyHasTray())
    {
        if (showTip)
        {
            UIOperation::getIns()->showTip(tr("Dispense work holder already detected tray!"));
        }
        return;
    }
    getNewTrayImpl();
}

void DispenseWorkHolder::returnBackTray()
{
    checkIsInit();

    makeSureEntranceHasNotTray();
    if (!detectReallyHasTray())
    {
        UIOperation::getIns()->showTip(tr("Did not detect tray!"));
        return;
    }
    dispWHBelt->velocityMove(SCAxis::oppositeDir(beltRunDir));
    ErrorHandler::tryToHandleGeneralError<void>(
        [this] {
            QElapsedTimer timer;
            timer.start();
            while (true)
            {
                if (dispWHLeftTraySensor->get(-1))
                {
                    dispWHBelt->stop();
                    break;
                }
                if (timer.elapsed() > dispWHConfig->getNewTrayTimeout())
                {
                    dispWHBelt->stop();
                    throw GeneralError(workerName(), tr("Return back tray timeout!"));
                }
                QThread::msleep(10);
            }
        },
        [this] { dispWHBelt->velocityMove(SCAxis::oppositeDir(beltRunDir)); });
    whState->setDispWHHasTray(false);
    UIOperation::getIns()->showTip(tr("Please take away the tray at the entrance of dispense work holder!"));
}

void DispenseWorkHolder::manuallyPushOutTray()
{
    checkIsInit();

    if (!detectReallyHasTray())
    {
        UIOperation::getIns()->showTip(tr("Did not detect tray!"));
        return;
    }

    pushOutTrayImpl();
}

void DispenseWorkHolder::upBeltZ()
{
    dispWHVacuum->set(false);
    dispWHBeltZModule->moveTo(SAxisPosName::UpPos);
}

void DispenseWorkHolder::downBeltZ()
{
    dispWHBeltZModule->moveTo(SAxisPosName::DownPos);
    dispWHVacuum->set(true);
}

void DispenseWorkHolder::getNewTrayImpl()
{
    dispWHStopperCyl->set(true, false);

    upBeltZ();

    if (stie == nullptr)
    {
        while (!dispWHLeftTraySensor->get(-1))
        {
            if (isStopping)
            {
                handleStop();
                return;
            }
            UIOperation::getIns()->getUIResponse("", tr("Please place tray at the entrance of dispense work holder!"),
                                                 MsgBoxIcon::Information, OkBtn);

            if (hasAbortEvent())
            {
                return;
            }
        }
    }
    else
    {
        while (true)
        {
            stie->moveToNextInitTrayPos();
            if (isStopping)
            {
                handleStop();
                return;
            }
            if (hasAbortEvent())
            {
                return;
            }
            stie->kickTray();
            if (dispWHLeftTraySensor->get(-1))
            {
                stie->setCurrentMagTrayState(MagTrayState::InUse);
                break;
            }
            else
            {
                stie->setCurrentMagTrayState(MagTrayState::NoTray);
                stie->retractKicker();
            }
        }
        stie->pushEvent(WorkerEvent("retractKicker"));
        stie->pushEvent(WorkerEvent("moveToNextInitTrayPos"));
    }

    dispWHStopperCyl->wait(true);

    dispWHBelt->velocityMove(beltRunDir);
    ErrorHandler::tryToHandleGeneralError<void>(
        [this] {
            QElapsedTimer timer;
            timer.start();
            while (true)
            {
                if (dispWHStageTraySensor->get(-1))
                {
                    dispWHBelt->stop();
                    break;
                }
                if (timer.elapsed() > dispWHConfig->getNewTrayTimeout())
                {
                    dispWHBelt->stop();
                    throw GeneralError(workerName(), tr("Get new tray timeout!"));
                }
                QThread::msleep(10);
            }
        },
        [this] { dispWHBelt->velocityMove(beltRunDir); });

    whState->setDispWHHasTray(true);
    whState->setDispWHTrayHasDoneWork(false);
    sensorTrayMap->reset();
    downBeltZ();
}

void DispenseWorkHolder::pushOutTrayImpl()
{
    dispWHStopperCyl->set(false, false);
    upBeltZ();
    makeSureEntranceHasNotTray();
    dispWHStopperCyl->wait(false);

    dispWHBelt->velocityMove(beltRunDir);
    ErrorHandler::tryToHandleGeneralError<void>(
        [this] {
            QElapsedTimer timer;
            timer.start();
            while (true)
            {
                if (!dispWHStageTraySensor->get())
                {
                    if (dispWHConfig->delayAfterPushoutTray() > 0)
                    {
                        QThread::msleep(dispWHConfig->delayAfterPushoutTray());
                    }
                    dispWHBelt->stop();
                    break;
                }
                if (timer.elapsed() > dispWHConfig->pushOutTrayTimeout())
                {
                    dispWHBelt->stop();
                    throw GeneralError(workerName(), tr("Push out tray timeout!"));
                }
                QThread::msleep(10);
            }
        },
        [this] { dispWHBelt->velocityMove(beltRunDir); });

    dispWHStopperCyl->set(true, false);
    whState->setDispWHHasTray(false);
    whState->setDispWHTrayHasDoneWork(false);

    QVariantList args;
    args << sensorTrayMap->toJson();
    emit sendEvent(WorkerName::BonderWorkHolder, WorkerEvent("setSensorTrayMap", args));
}

void DispenseWorkHolder::synchronizeHasTray()
{
    bool reallyHasTray = detectReallyHasTray();
    if (reallyHasTray)
    {
        downBeltZ();
    }
    if (reallyHasTray == whState->dispWHHasTray())
    {
        return;
    }
    else
    {
        qCWarning(logCate()) << tr("Software remembered has tray: %1, Really has tray: %2. Now sycnchronize "
                                   "HasTrayState!")
                                    .arg(whState->dispWHHasTray())
                                    .arg(reallyHasTray);
        whState->setDispWHHasTray(reallyHasTray);
    }
}

bool DispenseWorkHolder::detectReallyHasTray()
{
    upBeltZ();
    if (dispWHStageTraySensor->get(-1))
    {
        return true;
    }
    dispWHBelt->velocityMove(beltRunDir);
    QElapsedTimer timer;
    timer.start();
    while (timer.elapsed() < dispWHConfig->detectHasTrayMaxTime())
    {
        if (dispWHStageTraySensor->get(-1))
        {
            break;
        }
        QThread::msleep(10);
    }
    dispWHBelt->stop();
    return dispWHStageTraySensor->get(-1);
}

void DispenseWorkHolder::makeSureEntranceHasNotTray()
{
    ErrorHandler::tryToHandleGeneralError<void>([this] {
        if (dispWHLeftTraySensor->get())
        {
            throw GeneralError(workerName(), tr("Detected tray at the entrance of dispense work holder!"), false,
                               tr("Please take away the tray at the entrance first!"));
        }
    });
}

void DispenseWorkHolder::handleStop()
{
    clearEvent();
    stopEventHandled();
    sendEvent(WorkerName::BonderWorkHolder, WorkerEvent("dispWHStopped"));
}

DispWHConfig *DispenseWorkHolder::getDispWHConfig() const
{
    return dispWHConfig;
}

TrayMap *DispenseWorkHolder::getTrayMap() const
{
    return sensorTrayMap;
}
